#include <string.h>
#include "redtupleobject.h"
#include "redexceptionobject.h"
#include "redboundmethodobject.h"

static void boundmethod_clear(RedObject *self)
{
	Red_UNREF(RedBoundMethod_Self(self));
	Red_UNREF(RedBoundMethod_Callable(self));
}

static void boundmethod_release(RedObject *self)
{
	boundmethod_clear(self);
	Red_RELEASE();
}

static void boundmethod_traverse(RedObject *self, tp_visit_fn_t visit, void *args)
{
	Red_VISIT(RedBoundMethod_Self(self));
	Red_VISIT(RedBoundMethod_Callable(self));
}

RedTypeObject RedType_BoundMethod =
{
	RedType_INIT("bound_method", RedBoundMethodObject, RedType_Object)

	.tp_clear		= boundmethod_clear,
	.tp_release		= boundmethod_release,
	.tp_traverse	= boundmethod_traverse,
};

RedObject *RedBoundMethod_Invoke(RedObject *self, RedObject *args)
{
	if (!RedTuple_Exact(args))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'tuple' expected for 'args' argument");
		return NULL;
	}

	if (!RedBoundMethod_Exact(self))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'bound_method' expected for 'self' argument");
		return NULL;
	}

	RedObject *tuple = RedTuple_FromLength(RedTuple_Length(args) + 1);

	if (!tuple)
		return NULL;

	Red_REF(RedBoundMethod_Self(self));
	RedTuple_Items(tuple)[0] = RedBoundMethod_Self(self);

	for (int i = 0; i < RedTuple_Length(args); i++)
	{
		Red_REF(RedTuple_Items(args)[i]);
		RedTuple_Items(tuple)[i + 1] = RedTuple_Items(args)[i];
	}

	RedObject *result = RedObject_Invoke(RedBoundMethod_Callable(self), tuple);

	Red_UNREF(tuple);
	return result;
}

RedObject *RedBoundMethod_BindSelf(RedObject *callable, RedObject *self)
{
	RedObject *result = RedObject_Create(&RedType_BoundMethod, NULL);

	if (!result)
		return NULL;

	Red_REF(self);
	RedBoundMethod_Self(result) = self;
	RedBoundMethod_Callable(result) = callable;
	return result;
}
